
from decimal import Decimal
import json
import sys
import subprocess, signal, os
import datetime as dt
import numpy as np

from sklearn import metrics

from pathlib import Path


class MyTools:

    @staticmethod
    def purity_score(y_true, y_pred):
        # compute contingency matrix (also called confusion matrix)
        contingency_matrix = metrics.cluster.contingency_matrix(y_true, y_pred)
        # return purity
        return np.sum(np.amax(contingency_matrix, axis=0)) / np.sum(contingency_matrix)

    @staticmethod
    def getClass(classPath):
        '''

        :param classPath:  is like Mytools.MyTools, this first MyTools is a name of module (file name)
                           the second MyTools is the class name
        :return: the class which can be used to create objects of the class.
        '''
        classPath = 'view.endSrc.' + classPath
        parts = classPath.split('.')
        module = ".".join(parts[:-1])

        m = __import__(name=module, fromlist=[parts[-2]])
        # ------------------2020.08.07 fix bug and comment by Chen Hongjie
        # When the name variable is of the form package.module,
        # normally, the top-level package (the name up till the first dot) is returned,
        # not the module named by name.
        # However, when a non-empty fromlist argument is given, "the module" named by name is returned.
        # https://docs.python.org/3/library/functions.html#__import__
        m = getattr(m, parts[-1])
        return m

    @staticmethod
    def removeFile(pathFileName):
        p = Path(pathFileName)
        # if not p.is_file():
        #     return
        try:
            p.unlink()
            # Path.unlink(missing_ok=False)
            # Remove this file or symbolic link. If the path points to a directory, use Path.rmdir() instead.
            # If missing_ok is false (the default), FileNotFoundError is raised if the path does not exist.
            # If missing_ok is true, FileNotFoundError exceptions will be ignored (same behavior as the POSIX rm -f command).
            return True
        except Exception as e:
            print('MyTools.removeFile..failed to remove ' + pathFileName + ', exception is: ' + e)
            return False

    @staticmethod
    def toFloatIfNotNone(val):
        return float(val) if val else val

    @staticmethod
    def isJsonDumpable(dictOrListOrScalar):

        # 1) case of scalar
        if isinstance(dictOrListOrScalar, int) or \
                isinstance(dictOrListOrScalar, float) or \
                isinstance(dictOrListOrScalar, bool) or \
                isinstance(dictOrListOrScalar, str) or \
                dictOrListOrScalar is None:
            return True

        # 2) case of np.ndarray
        if isinstance(dictOrListOrScalar, np.ndarray):
            print('np.ndarray found: return False')
            return False

        # 3) case of container
        if isinstance(dictOrListOrScalar, list):
            for a in dictOrListOrScalar:
                if not MyTools.isJsonDumpable(a):
                    return False  # found one of np.ndarray or other
            return True

        if isinstance(dictOrListOrScalar, dict):
            for v in dictOrListOrScalar.values():
                if not MyTools.isJsonDumpable(v):
                    return False
            return True

        # 4) case of unknown
        print('unknown found: ', dictOrListOrScalar, ', return False')
        return False  # do not know the type

    @staticmethod
    def checkMemberShape(memberList, memberShape, ListName):

        for m in memberList:
            assert np.shape(m) == memberShape, 'each member of ' + ListName + \
                  ' must have the same shape: ' + str(memberShape)

    @staticmethod
    def getDatetimeFileName():
        cur = str(dt.datetime.now())

        cur = cur.replace('-', '_')
        cur = cur.replace(' ', '_')
        cur = cur.replace(':', '_')
        cur = cur.replace('.', '_')
        return cur

    @staticmethod
    def getRound(floatNum, precision=4):
        return float(round(Decimal(floatNum), precision))
    @staticmethod
    def checkEqual(pred, actual, msg):
        diff = abs(pred - actual)
        assert diff < 1e-8, \
            "pred=" + str(pred) + ", actal=" + str(actual) + ' : ' + msg

    @staticmethod
    def checkLessEqualThan(pred, actual, msg):
        assert pred <= actual, \
            "pred=" + str(pred) + ", actal=" + str(actual) + ' : ' + msg

    @staticmethod
    def checkBigEqualThan(pred, actual, msg):
        assert pred >= actual, \
            "pred=" + str(pred) + ", actal=" + str(actual) + ' : ' + msg

    @staticmethod
    def insertSortedTupleList(a, tup, lo=0, hi=None):
        if lo < 0:
            raise ValueError('lo must be non-negative')
        if hi is None:
            hi = len(a)
        while lo < hi:
            mid = (lo + hi) // 2
            if a[mid][0] < tup[0]:
                lo = mid + 1
            else:
                hi = mid
        a.insert(lo, tup)

    @staticmethod
    def insertSortedList(a, ele, lo=0, hi=None):
        if lo < 0:
            raise ValueError('lo must be non-negative')
        if hi is None:
            hi = len(a)
        while lo < hi:
            mid = (lo + hi) // 2
            if a[mid] < ele:
                lo = mid + 1
            else:
                hi = mid
        a.insert(lo, ele)


    @staticmethod
    def getDicByJson(astr, debug=True, info=''):
        if astr is None:
            return None

        if type(astr) is dict:
            return astr

        astr = str(astr)
        astr = astr.replace("\'", "\"")
        dic = json.loads(astr)
        return dic

    @staticmethod
    def getJsonByDict(adict):
        if adict is None:
            return None

        assert MyTools.isJsonDumpable(adict)
        return json.dumps(adict)

    @staticmethod
    def getLogDir():
        startPath = sys.argv[0]
        # print('arg0: ', startPath)
        endIndex = startPath.rfind('/')
        srcDir = startPath[0:endIndex]
        srcDir += '/log/'
        # print(srcDir)
        return srcDir

    @staticmethod
    def getBrotherDir(name):
        startPath = sys.argv[0]
        endIndex = startPath.rfind('/')
        assert endIndex != 0, 'MyTools.getBrotherDir(' + name + ")..failed to find last dir"

        # print('L: ', startPath[0: endIndex])

        endIndex = (startPath[0: endIndex]).rfind('/')
        assert endIndex != -1, 'MyTools.getBrotherDir(' + name + ")..failed to find next to last dir"

        srcDir = startPath[0: endIndex]
        srcDir += '/' + name + '/'
        return srcDir

    @staticmethod
    def getStartDir():
        startPath = sys.argv[0]
        endIndex = startPath.rfind('/')
        srcDir = startPath[0:endIndex]
        return srcDir + '/'

    @staticmethod
    def killProcess(pidStr):
        p = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
        out, err = p.communicate()
        for line in out.splitlines():
            line = line.decode("utf-8")
            line = line.lower()
            lst = line.split()
            if lst[1] == pidStr:
                pid = int(lst[1])
                print('killProcess......kill...pid=', pid)
                try:
                    os.kill(pid, signal.SIGKILL)
                except Exception as e:
                    print('----killProcess----------> ', e)

            # if '?' in line \
            #    and 'jianfei' in line \
            #    and 'python3' in line \
            #    and 'startOld.py' in line:
            #     lst = line.split()
            #     # print(str(lst[1]))
            #     pid = int(lst[1])
            #     os.kill(pid, signal.SIGKILL)
            #     continue

    @staticmethod
    def killProcessIfNotPid(pidStr, key1):
        p = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
        out, err = p.communicate()
        for line in out.splitlines():
            line = line.decode("utf-8")
            line = line.lower()
            if key1 in line:
                lst = line.split()
                if lst[1] != pidStr:
                    pid = int(lst[1])
                    print('killProcessIfNotPid......kill...pid=', pid)
                    try:
                        os.kill(pid, signal.SIGKILL)
                    except Exception as e:
                        print('----killProcessIfNotPid----------> ', e)

    # @staticmethod
    # def killProcessByciKey(caseInsensivekey):
    #     p = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
    #     out, err = p.communicate()
    #     for line in out.splitlines():
    #         line = line.decode("utf-8")
    #         line = line.lower()
    #         key = caseInsensivekey.lower()
    #         if key in line:
    #             lst = line.split()
    #             pid = int(lst[1])
    #             print('killProcessByciKey......kill...pid=', pid, 'key= ', caseInsensivekey)
    #             try:
    #                 os.kill(pid, signal.SIGKILL)
    #             except Exception as e:
    #                 print('----killProcessByciKey----------> ', e)

    @staticmethod
    def getPids(caseInsensivekey):
        p = subprocess.Popen(['ps', '-ef'], stdout=subprocess.PIPE)
        out, err = p.communicate()
        pids = []
        for line in out.splitlines():
            line = line.decode("utf-8")
            line = line.lower()
            key = caseInsensivekey.lower()
            if key in line:
                lst = line.split()
                pid = int(lst[1])
                pids.append(pid)
        return pids

    @staticmethod
    def killProcessByPids(pids):
        for p in pids:
            try:
                os.kill(p, signal.SIGKILL)
            except Exception as e:
                print('----killProcessByPids..pid=', p, '..failed: ', e)


    @staticmethod
    def updateJsonStore(dic, fileName):
        path = MyTools.getStartDir() + 'store/'
        jfile = None
        try:
            open(path + fileName, 'w').close()      # clear content
            jfile = open(path + fileName, 'a+')
        except Exception as e:
            print('>>>>> updateJsonStore...failed for', fileName, '..', e)
            return

        r = json.dumps(dic)
        jfile.write(r)
        jfile.flush()
        jfile.close()

    @staticmethod
    def getJsonStore(fileName):
        path = MyTools.getStartDir() + 'store/'
        jfile = None
        try:
            jfile = open(path + fileName, 'r')
        except Exception as e:
            print('>>>>> getJsonStore...failed for', fileName, '..', e)
            return None

        dic = json.load(jfile)
        dic = MyTools.getDicByJson(dic)
        return dic


if __name__ == '__main__':

    p = MyTools.getBrotherDir('data')
    print(p)